﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public enum JOINT_TYPE
{
    HINGE, FIXED, SPRING, CONFIGURABLE 
}

public enum LAST_TYPE
{
    NONE, BODY, JOINT
}

public class LSystem
{
    Dictionary<char, string> productionRules = new Dictionary<char, string>();
    public static char[] bodies = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' }; //, 'G', 'H' };
    public static char[] joints = new char[] { '1', '2', '3', '4' }; //, '5', '6' };
    public static char[] configurations = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; //, 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y', 'z' };

    public char[] all;

    public LSystem()
    {
        all = new char[bodies.Length + joints.Length + configurations.Length];
        bodies.CopyTo(all, 0);
        joints.CopyTo(all, bodies.Length);
        configurations.CopyTo(all, bodies.Length + joints.Length);

    }

    public LSystem(int depth, ref Dictionary<char, string> _productionRules, ref string _axiom, ref string _system)
    {
        all = new char[bodies.Length + joints.Length + configurations.Length];
        bodies.CopyTo(all, 0);
        joints.CopyTo(all, bodies.Length);
        configurations.CopyTo(all, bodies.Length + joints.Length);

        GenerateRules();

        string axiom = "";
        string system = "";
        foreach (char c in bodies)
        {
            if (productionRules.ContainsKey(c))
            {
                axiom = "" + c + configurations[Random.Range(0, configurations.Length)];
                system = Expand(axiom, depth);
                break;
            }
        }

        //Pass them on as the er referenced 
        _system = system;
        _axiom = axiom;
        _productionRules = productionRules;
    }

    /// <summary>
    /// Updates this instance of LSystem with the new axiom and production rules from the genotype.
    /// Used for mutation and crossovers
    /// </summary>
    /// <param name="productionRules">New production rules</param>
    /// <param name="axiom">Start axiom</param>
    /// <param name="depth">Expand depth</param>
    /// <returns>The expanded LSystem</returns>
    public string CreateFromGenoType(Dictionary<char, string> productionRules, string axiom, int depth)
    {
        this.productionRules = productionRules;
        return Expand(axiom, depth);
    }

    /// <summary>
    /// Production rules for L System. Joints are lower case, Objects are upper case.
    /// Each joint has to be sandwiched between two objects. Start and end symbol must be objects.
    /// Any symbols not appearing on LHS are terminal symbols.
    /// Object rules has to start and end with objects.
    /// Joint rules has to start and end with joints.
    /// Start axioms must be valid (eg. start and end with objects)
    /// Brackets signal push / pop functionality: [: push to stack, ]: pop from stack - must be paired!
    /// Each push / pop combination moves the drawing angle by a fixed rotation
    /// Pop and push must come after an object
    /// 
    /// Example:
    /// Rules: A --> AbC, b --> bCa
    /// Axiom: AbA (or just A)
    /// </summary>
    void CreateProductionRules()
    {
        productionRules.Add('A', "A[bA]");
        productionRules.Add('b', "bCa");
    }

    void GenerateRules()
    {
        productionRules = new Dictionary<char, string>();

        foreach (char c in all)
        {
            float prob = Random.Range(0, 1.0f);

            if (prob < 0.6f)
            {
                string rule = "";

                if (char.IsUpper(c))
                {
                    // Is body
                    rule = BodyRule();
                }
                else if (char.IsLower(c))
                {
                    // Is configuration
                    rule = ConfigurationRule();
                }
                else
                {
                    // Is joint
                    rule = JointRule();
                }
                productionRules.Add(c, rule);
            }
        }
    }

    public static string AddBodyRule()
    {
        return bodies[Random.Range(0, bodies.Length)] + "" + configurations[Random.Range(0, configurations.Length)];
    }

    public static string AddJointRule()
    {
        return joints[Random.Range(0, joints.Length)] + "" + configurations[Random.Range(0, configurations.Length)];
    }

    public static string ConfigurationRule()
    {
        return "" + configurations[Random.Range(0, configurations.Length)];
    }

    public static char RandomBody()
    {
        return bodies[Random.Range(0, bodies.Length)];
    }

    public static char RandomJoint()
    {
        return joints[Random.Range(0, joints.Length)];
    }

    public static char RandomConfiguration()
    {
        return configurations[Random.Range(0, configurations.Length)];
    }

    public string CreateRule(char c)
    {
        string rule = "";
        if (char.IsUpper(c))
        {
            // Is body
            rule = BodyRule();
        }
        else if (char.IsLower(c))
        {
            // Is configuration
            rule = ConfigurationRule();
        }
        else
        {
            // Is joint
            rule = JointRule();
        }
        return rule;
    }

    private string BodyRule()
    {
        string rule = bodies[Random.Range(0, bodies.Length)] + "";
        float threshold = 0.6f;
        float prob = Random.Range(0, 1.0f);
        float bracket_threshold = 0.5f;
        bool hasBracket = false;

        prob = Random.Range(0, 1.0f);

        int i = 4;
        while (prob < threshold)
        {
            // Add configuration
            rule += configurations[Random.Range(0, configurations.Length)];
            prob = Random.Range(0, 1.0f);
            if (prob < bracket_threshold)
            {
                if (hasBracket)
                {
                    rule += "]";
                    hasBracket = false;
                }
                else
                {
                    rule += "[";
                    hasBracket = true;
                }
            }

            // Add joint and object
            rule += AddJointRule();
            rule += bodies[Random.Range(0, bodies.Length)] + "";

            i++;
            prob = Random.Range(0, 1.0f);
            threshold -= 1 / i;
        }
        if (hasBracket)
        {
            if (rule[rule.Length - 4] == '[')
            {
                //   print("Yo dawg");
                rule += configurations[Random.Range(0, configurations.Length)];
                rule += "]";
                rule += AddJointRule();
                rule += bodies[Random.Range(0, bodies.Length)];
            }
            else
            {
                rule = rule.Insert(rule.Length - 3, "]");
                hasBracket = false;
            }
        }

        return rule;
    }

    private string JointRule()
    {
        string rule = joints[Random.Range(0, joints.Length)] + "";
        float threshold = 0.6f;
        float prob = Random.Range(0, 1.0f);
        float bracket_threshold = 0.40f;
        bool hasBracket = false;

        int i = 4;
        while (prob < threshold)
        {
            // Add configuration
            rule += configurations[Random.Range(0, configurations.Length)];
            rule += AddBodyRule();
            prob = Random.Range(0, 1.0f);
            if (prob < bracket_threshold)
            {
                if (hasBracket)
                {
                    rule += "]";
                    hasBracket = false;
                }
                else
                {
                    rule += "[";
                    hasBracket = true;
                }
            }

            // Add body and joint

            rule += joints[Random.Range(0, joints.Length)] + "";

            i++;
            prob = Random.Range(0, 1.0f);
            threshold -= 1 / i;
        }
        //if (hasBracket)
        //{
        //    rule = rule.Insert(rule.Length - 1, "]");
        //    hasBracket = false;
        //}

        if (hasBracket)
        {
            if (rule[rule.Length - 4] == '[')
            {
                rule += configurations[Random.Range(0, configurations.Length)];
                rule += "]";
                rule += AddJointRule();
                rule += bodies[Random.Range(0, bodies.Length)];
            }
            else
            {
                rule = rule.Insert(rule.Length - 1, "]");
                hasBracket = false;
            }
        }

        return rule;
    }

    private string Expand(string axe, int depth)
    {
        string axiom = axe;

        for (int i = 0; i < depth; i++)
        {
            char[] parts = axiom.ToCharArray();
            axiom = "";

            foreach (char p in parts)
            {
                if (productionRules.ContainsKey(p))
                {
                    axiom += productionRules[p];
                }
                else
                {
                    axiom += p;
                }
            }
        }
        return axiom;
    }

}

